﻿//*** Guidance generated code ***//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

using ProjectBase.Core;

using $prjsn$.Common;
using $prjsn$.Common.Validation;
using $prjsn$.Common.Data;

namespace $safeprojectname$
{
    public class UniquePropertyRule<TEntity, TID, TFilter> : RuleBase, IValidationRule<TEntity>
        where TEntity : class, IPersistentObjectWithTypedId<TID>
        where TFilter : class, IFilter<TID>, new()
    {
        #region members

        private bool _isRequired;

        #endregion members

        #region Constructor

        public UniquePropertyRule(string propertyName, bool isRequired)
        {
            CheckProperties(propertyName);

            PropertyName = propertyName;
            _isRequired = isRequired;
        }

        private static void CheckProperties(string propertyName)
        {
            if (typeof(TEntity).GetProperty(propertyName).IsNull())
            {
                throw new ArgumentOutOfRangeException("The property name [{0}] of the entity [{1}] does not exist."
                                                          .FormatWith(propertyName, typeof(TEntity).Name));
            }
            if (typeof(TFilter).GetProperty(propertyName).IsNull())
            {
                throw new ArgumentOutOfRangeException("The parameter [{0}] of the Filter [{1}] does not exist."
                                                          .FormatWith(propertyName, typeof(TFilter).Name));
            }
        }

        #endregion Constructor

        #region Validate

        public virtual ValidationResult Validate(TEntity entity, out ValidationMessage message)
        {
            message = null;
            var dao = ProjectBase.Core.Service.Factory.CreateInstance<IBaseDao<TEntity, TID, TFilter>>();

            var filter = new TFilter
            {
                TakeRows = 1,
            };

            var entityProperty = typeof(TEntity).GetProperty(PropertyName);
            var filterProperty = typeof(TFilter).GetProperty(PropertyName);

            var value = entityProperty.GetValue(entity, null);

            if (!IsRequired)
            {
                // ValueType
                var canBeEmpty = !entityProperty.PropertyType.IsValueType
                                 && (value).IsNull();
                // string
                canBeEmpty |= entityProperty.PropertyType.HasType<string>()
                              && (value as string).IsEmpty();

                if (canBeEmpty)
                {
                    return ValidationResult.IsValid;
                }

                var canBeNull = entityProperty.PropertyType.IsValueType
                                && entityProperty.PropertyType.IsGenericType
                                && value.IsNull();

                if (canBeNull)
                {
                    return ValidationResult.IsValid;
                }
            }

            if (!TrySetValue(filterProperty, filter, value))
            {
                message = new ValidationMessage(Str.Messages.BindingTypeError3)
                {
                    PropertyName,
                    filterProperty.PropertyType.Name,
                    value.Is() ? value.GetType().Name : "UnKnown" ,
                };
                return ValidationResult.Error;
            }

            var original = dao.Find(entity.ID, filter).FirstOrDefault();
            if (original.Is())
            {
                message = new ValidationMessage(Str.Messages.ItemAlreadyExists3)
                {
                    original.GetType().Name,
                    original.IDToDisplay,
                    PropertyName,
                };
                return ValidationResult.Error;
            }
            return ValidationResult.IsValid;
        }

        protected virtual bool TrySetValue(PropertyInfo filterProperty, TFilter filter, object value)
        {
            var type = filterProperty.PropertyType;
            var isSimple = type.HasType<string>()
                           || type.IsValueType;

            if (isSimple)
            {
                filterProperty.SetValue(filter, value, null);
                return true;
            }

            var isList = type.HasType<System.Collections.IEnumerable>()
                         && type.IsGenericType;

            if (!isList)
            {
                return false;
            }

            // create the list e.g. List<int?>
            var parameterType = type.GetGenericArguments()[0];
            var listType = typeof(List<>).MakeGenericType(parameterType);
            var addMethod = listType.GetMethod("Add");

            var list = Activator.CreateInstance(listType);

            addMethod.Invoke(list, new[] { value });

            filterProperty.SetValue(filter, list, null);
            return true;
        }

        #endregion Validate

        #region properties

        protected virtual bool IsRequired
        {
            get { return _isRequired; }
            set { _isRequired = value; }
        }

        #endregion properties
    }
}